/**
 * 
 */
package com.kddi.muldev.op;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openid4java.association.AssociationException;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.AuthSuccess;
import org.openid4java.message.DirectError;
import org.openid4java.message.Message;
import org.openid4java.message.MessageException;
import org.openid4java.message.MessageExtension;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.ax.FetchRequest;
import org.openid4java.message.ax.FetchResponse;
import org.openid4java.message.sreg.SRegMessage;
import org.openid4java.message.sreg.SRegRequest;
import org.openid4java.message.sreg.SRegResponse;
import org.openid4java.server.InMemoryServerAssociationStore;
import org.openid4java.server.ServerException;
import org.openid4java.server.ServerManager;

/**
 * @author liuqia
 *
 */
public class ProviderServlet extends HttpServlet {

	private static final long serialVersionUID = -1600132457371905700L;
	
	private static final Log LOG = LogFactory.getLog(ProviderServlet.class);
	
	public static final String DATA_FILE = "/opt/tomcat/openid_registration_info.properties";

	private ServletContext context;
	private ServerManager manager;
	
	private static final String[] INFO_ITEMS = {"loginid","password","nickname","email","fullname",
		"dob","gender","postcode","country","language","timezone","auoneid"};
	private static final Map<String, Integer> INFO_NAME_INDEX_MAP = new HashMap<String, Integer>();
	static{
		for (int i=0; i<INFO_ITEMS.length; i++) {
			INFO_NAME_INDEX_MAP.put(INFO_ITEMS[i], i);
		}
	}
	/**
	 * {@inheritDoc}
	 */
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		context = config.getServletContext();
		LOG.debug("context: " + context);
		manager=new ServerManager();
		manager.setSharedAssociations(new InMemoryServerAssociationStore());
		manager.setPrivateAssociations(new InMemoryServerAssociationStore());
		LOG.debug("Initialized ServerManager...");
	}

	/* (non-Javadoc)
	 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		this.doPost(req, resp);
	}

	/* (non-Javadoc)
	 * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		String opEndpointUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() +context.getContextPath()+ "/auth";
		manager.setOPEndpointUrl(opEndpointUrl);
		LOG.debug("=====> Set ServerManager's OPEndpointUrl: " + opEndpointUrl);
		
		ParameterList requestp;
		HttpSession session = request.getSession();
		
		// Completing the authz and authn process by redirecting here
	    if ("complete".equals(request.getAttribute("_action"))) {
	    	LOG.debug("=====> Get ParameterList from session...");
	        requestp=(ParameterList) session.getAttribute("parameterlist"); 
	    }else{
	    	LOG.debug("=====> Get ParameterList from request...");
	        requestp = new ParameterList(request.getParameterMap());
	    }
	    
	    LOG.debug("=====> Generated parameter list:\n"+requestp.toString());

	    String mode = requestp.hasParameter("openid.mode") ? requestp.getParameterValue("openid.mode") : null;
	    Message responsem;
        String responseText;
        LOG.debug("=====> current openid.mode is:" + mode);

        if ("associate".equals(mode)){
            LOG.debug("=====> process an association request..."); 
            responsem = manager.associationResponse(requestp);
            responseText = responsem.keyValueFormEncoding();
        }else if ("checkid_setup".equals(mode) || "checkid_immediate".equals(mode)) {
            // interact with the user and obtain data needed to continue
            //List userData = userInteraction(requestp);
            String userSelectedId = null;
            String userSelectedClaimedId = null;
            Boolean authenticatedAndApproved = Boolean.FALSE;

            if ((session.getAttribute("authenticatedAndApproved") == null) ||
                    (((Boolean)session.getAttribute("authenticatedAndApproved")) == Boolean.FALSE) ) {
            	LOG.debug("=====> request forward to login page...");
                session.setAttribute("parameterlist", requestp);
                RequestDispatcher rd = request.getRequestDispatcher("/login");
                rd.forward(request, response);
                return;
            }else{
                userSelectedId = (String) session.getAttribute("openid.claimed_id");
                userSelectedClaimedId = (String) session.getAttribute("openid.identity");
                authenticatedAndApproved = (Boolean) session.getAttribute("authenticatedAndApproved");
                // Remove the parameterlist so this provider can accept requests from elsewhere
                session.removeAttribute("parameterlist");
                // Makes you authorize each and every time
                session.setAttribute("authenticatedAndApproved", Boolean.FALSE); 
            }

            LOG.debug("--- process an authentication request ---");
            responsem = manager.authResponse(requestp, userSelectedId, userSelectedClaimedId,
                    authenticatedAndApproved.booleanValue());

            // caller will need to decide which of the following to use:
            // - GET HTTP-redirect to the return_to URL
            // - HTML FORM Redirection
            //responseText = response.wwwFormEncoding();
            if (responsem instanceof AuthSuccess) {
            	AuthSuccess authSuccess = (AuthSuccess) responsem; 
            	processSimpleRegistrationAndAttributeExchangeExtension(requestp, authSuccess, requestp.getParameterValue("openid.identity"));
                response.sendRedirect(authSuccess.getDestinationUrl(true));
                return;
            }else{
                responseText="<pre>"+responsem.keyValueFormEncoding()+"</pre>";
            }
        }else if ("check_authentication".equals(mode)) {
            LOG.debug("=====> processing a verification request...");
            responsem = manager.verify(requestp);
            responseText = responsem.keyValueFormEncoding();
        }else{
            LOG.debug("--- error response ---");
            responsem = DirectError.createDirectError("Unknown request");
            responseText = responsem.keyValueFormEncoding();
        }
        LOG.debug("=====> response content is: \n" + responseText + "\n");
        writeResponse(responseText, response);
	}

	/**
	 * Process simple registration and attribute exchange extension
	 * @param requestp
	 */
	private void processSimpleRegistrationAndAttributeExchangeExtension(ParameterList requestp, 
			AuthSuccess authSuccess, String openId) {
		AuthRequest authRequest;
		try {
			authRequest = AuthRequest.createAuthRequest(requestp, manager.getRealmVerifier());
			String[] registerInfo = getRegisterInfo(openId);
			processSimpleRegistration(authRequest, authSuccess, registerInfo);
			processAttributeExchange(authRequest, authSuccess, registerInfo);
			manager.sign(authSuccess);
		} catch (MessageException e) {
			LOG.warn(e.getMessage(), e);
		} catch (ServerException e) {
			LOG.warn(e.getMessage(), e);
		} catch (AssociationException e) {
			LOG.warn(e.getMessage(), e);
		}
	}

	

	/**
	 * Process simple registration
	 * @param authRequest
	 * @param authSuccess
	 * @throws MessageException 
	 */
	private void processSimpleRegistration(AuthRequest authReq,
			AuthSuccess authSuccess, String[] registerInfo) throws MessageException {
		if (authReq.hasExtension(SRegMessage.OPENID_NS_SREG)){
		    MessageExtension ext = authReq.getExtension(SRegMessage.OPENID_NS_SREG);
		    if (ext instanceof SRegRequest){
		        SRegRequest sregReq = (SRegRequest) ext;
		        List<String> required = sregReq.getAttributes(true);
		        List<String> optional = sregReq.getAttributes(false);
		        Map<String, String> userData = new HashMap<String, String>();
		        for (String item : required) {
		        	userData.put(item, registerInfo[INFO_NAME_INDEX_MAP.get(item)]);
		        }
		        for (String item : optional) {
		        	userData.put(item, registerInfo[INFO_NAME_INDEX_MAP.get(item)]);
		        }
		        SRegResponse sregResp = SRegResponse.createSRegResponse(sregReq, userData);
		        authSuccess.addExtension(sregResp);
		    }
		}
	}
	
	/**
	 * Process attribute exchange
	 * @param authRequest
	 * @param authSuccess
	 * @throws MessageException 
	 */
	private void processAttributeExchange(AuthRequest authReq,
			AuthSuccess authSuccess, String[] registerInfo) throws MessageException {
		if (authReq.hasExtension(AxMessage.OPENID_NS_AX)){
		    MessageExtension ext = authReq.getExtension(AxMessage.OPENID_NS_AX);
		    if (ext instanceof FetchRequest){
		        FetchRequest fetchReq = (FetchRequest) ext;
		        Map<String, Object> required = fetchReq.getAttributes(true);
		        Map<String, Object> optional = fetchReq.getAttributes(false);
		        Map<String, String> userData = new HashMap<String, String>();
		        for (String key : required.keySet()) {
		        	userData.put(key, registerInfo[11]);
		        }
		        for (String key : optional.keySet()) {
		        	userData.put(key, registerInfo[11]);
		        }
		        FetchResponse fetchResp = FetchResponse.createFetchResponse(fetchReq, userData);
		        authSuccess.addExtension(fetchResp);
		    }
		}
	}

	/**
	 * Write Response
	 * @param respText String 
	 * @param response HttpServletResponse
	 * @throws IOException e
	 */
	private void writeResponse(String respText, HttpServletResponse response) throws IOException {
		response.setContentType("text/html");
		response.setCharacterEncoding("UTF-8");
		try {
			response.setContentLength(respText.getBytes("UTF-8").length);
		} catch (UnsupportedEncodingException e) {
			LOG.warn(e.getMessage(), e);
		}
		Writer out  = response.getWriter();
		out.write(respText);
		response.flushBuffer();
	}
	
	/**
	 * Get Register Info
	 * @param openId
	 * @return
	 */
	private String[] getRegisterInfo(String openId) {
		Properties props = new Properties();
		String defaultInfo = ",,,,,,,,,,,";
		String info = null;
		try {
			props.load(new FileInputStream(DATA_FILE));
			info = props.getProperty(encodeString(openId), defaultInfo);
		} catch (UnsupportedEncodingException e) {
			LOG.warn(e.getMessage(), e);
		} catch (IOException e) {
			LOG.warn(e.getMessage(), e);
		}
		if (info == null) {
			info = defaultInfo;
		}
		return info.split(",",12);
	}
	
	/**
	 * encodeString
	 * @param str
	 * @return
	 */
	private String encodeString(String str) {
		try {
			return URLEncoder.encode(str,"ISO-8859-1");
		} catch (UnsupportedEncodingException e) {
			LOG.warn(e.getMessage(), e);
			return "";
		}
	}

}
